/**
 * \file AddressSpace.cc
 * \brief Address space management
 * \author Corey Tabaka
 *
 */

/*
   Copyright 2007 Corey Tabaka

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include <ktypes.h>
#include <vm/AddressSpace.h>
#include <Log.h>
#include <platform/PageTable.h>
#include <platform/Memory.h>
#include <i386/mem.h>

Region::Region(AddressSpace *as, uint32 va, uint32 pa, uint32 size,
	uint32 flags, uint32 type) {
	this->va = va & ~PAGE_MASK;
	this->pa = pa & ~PAGE_MASK;
	this->size = PAGE_ROUND(size);
	this->flags = flags;
	this->type = type;
	addressSpace = as;
}

Region::~Region(void) {
}

uint32 Region::getPhysicalBase(void) {
	return pa;
}

uint32 Region::getVirtualBase(void) {
	return va;
}

uint32 Region::getSize(void) {
	return size;
}

uint32 Region::getType(void) {
	return type;
}

uint32 Region::getFlags(void) {
	return flags;
}

Region *Region::getNext(uint32 type) {
	for (Region *n=next; n; n = n->next) {
		if (n->type == type || type == (uint32) REGION_TYPE_NA) {
			return n;
		}
	}
	
	return 0;
}

AddressSpace::AddressSpace(void) {
}

AddressSpace::AddressSpace(PageTable *pt, PageMap *pm) : lock() {
	pageTable = pt;
	pageMap = pm;
	head = tail = 0;
}

AddressSpace::~AddressSpace(void) {
	for (Region *n=head; n;) {
		Region *tmp = n->next;
		delete n;
		n = tmp;
	}
}

void AddressSpace::insert(Region *region) {
	// insert into the list in order of virtual base
	Region *n = 0;
	for (n = head; n; n = n->next) {
		if (region->va < n->va) {
			region->prev = n->prev;
			n->prev = region;
			
			if (region->prev) {
				region->prev->next = region;
			} else {
				head = region;
			}
			
			region->next = n;
			break;
		}
	}
	
	if (!n) {
		if (tail) {
			region->prev = tail;
			region->next = 0;
			tail->next = region;
			tail = region;
		} else {
			region->prev = 0;
			region->next = 0;
			head = tail = region;
		}
	}
}

void AddressSpace::remove(Region *region) {
	for (Region *n=head; n; n = n->next) {
		if (region == n) {
			bool isHead = head == region;
			bool isTail = tail == region;
			
			if (isTail) {
				tail = region->prev;
				
				if (!isHead) {
					region->prev->next = 0;
				}
			}
			
			if (isHead) {
				head = region->next;
				
				if (!isTail) {
					region->next->prev = 0;
				}
			}
			
			if (!isHead && !isTail) {
				region->prev->next = region->next;
				region->next->prev = region->prev;
			}
			
			region->next = region->prev = 0;
			break;
		}
	}
}

Region *AddressSpace::create(uint32 va, uint32 pa, uint32 size, uint32 flags,
	uint32 type) {
	Region *region = new Region(this, va, pa, size, flags, type);
	
	lock.lock();
	
	// setup the appropriate page table/map entries based on flags and type
	switch (type) {
		case REGION_TYPE_VM86: {
			region->pa = (uint32) Memory::alloc(size);	
			if (region->pa) {
				Log::printf(LOG_MEDIUM, "Creating vm86 region pa=%08x va=%08x "
					"size=%08x\n", region->pa, region->va, region->size);
				
				pageMap->reserve(region->va, region->size);
				pageTable->acquire((void *) region->pa, (void *) region->va,
					region->size, true,
					ACQUIRE_MODE_KEEP_PDE | ACQUIRE_MODE_REPLACE_PTE);
			} else {
				Log::printf(LOG_HIGH,
					"%s:%d: unable to reserve %d system pages\n", __FILE__,
					__LINE__, region->size >> PAGE_SHIFT);
				
				delete region;
				
				lock.unlock();
				return 0;
			}	
		}
		break;
		
		case REGION_TYPE_CODE:
		case REGION_TYPE_DATA:
		case REGION_TYPE_BSS:
		case REGION_TYPE_HEAP: {
			region->pa = (uint32) Memory::alloc(size);	
			if (region->pa) {
				Log::printf(LOG_MEDIUM, "Creating region pa=%08x va=%08x "
					"size=%08x\n", region->pa, region->va, region->size);
				
				pageMap->reserve(region->va, region->size);
				pageTable->acquire((void *) region->pa, (void *) region->va,
					region->size, flags & REGION_FLAGS_USER,
					ACQUIRE_MODE_KEEP_PDE | ACQUIRE_MODE_REPLACE_PTE
					| (flags & REGION_FLAGS_READONLY ? ACQUIRE_MODE_READ_ONLY
					: 0));
				
				if (flags & REGION_FLAGS_ANONYMOUS) {
					memset((void *) region->pa, 0, region->size);
				}
			} else {
				Log::printf(LOG_HIGH,
					"%s:%d: unable to reserve %d system pages\n", __FILE__,
					__LINE__, region->size >> PAGE_SHIFT);
				
				delete region;
				
				lock.unlock();
				return 0;
			}	
		}
		break;
		
		case REGION_TYPE_MAPPING:
			if (flags & REGION_FLAGS_ANONYMOUS) {
				region->pa = (uint32) Memory::alloc(region->size);
				
				if (region->pa) {
					Log::printf(LOG_HIGH,
						"%s:%d: unable to reserve %d system pages\n", __FILE__,
						__LINE__, region->size >> PAGE_SHIFT);
					
					delete region;
					
					lock.unlock();
					return 0;
				}
			}
			pageMap->reserve(region->va, region->size);
			pageTable->acquire((void *) region->pa, (void *) region->va,
				region->size, flags & REGION_FLAGS_USER,
				ACQUIRE_MODE_KEEP_PDE | ACQUIRE_MODE_REPLACE_PTE
				| (flags & REGION_FLAGS_READONLY ? ACQUIRE_MODE_READ_ONLY : 0)
				| (flags & REGION_FLAGS_NOCACHE ? ACQUIRE_MODE_DISABLE_CACHE
				: 0));
			break;
		
		case REGION_TYPE_FRAMEBUFFER:
		case REGION_TYPE_VGA:
			pageTable->acquire((void *) region->pa, (void *) region->va,
				region->size, flags & REGION_FLAGS_USER,
				ACQUIRE_MODE_KEEP_PDE | ACQUIRE_MODE_REPLACE_PTE);
			break;
		
		default:
			Log::printf(LOG_HIGH, "Unknown region type %d\n", type);
			break;
	}
	
	insert(region);
	
	lock.unlock();
	
	return region;
}

void AddressSpace::destroy(Region *region) {
	lock.lock();
	
	remove(region);
	
	switch (region->type) {
		case REGION_TYPE_VM86:
		case REGION_TYPE_CODE:
		case REGION_TYPE_DATA:
		case REGION_TYPE_BSS:
		case REGION_TYPE_HEAP:
			if (region->pa) {
				Memory::free((void *) region->pa, region->size);
				pageMap->free(region->va, region->size);
				pageTable->release((void *) region->va, region->size);
			}
			break;
		
		case REGION_TYPE_MAPPING:
			if (region->flags & REGION_FLAGS_ANONYMOUS) {
				Memory::free((void *) region->pa, region->size);
			}
			pageMap->free(region->va, region->size);
			pageTable->release((void *) region->va, region->size);
			break;
		
		case REGION_TYPE_FRAMEBUFFER:
		case REGION_TYPE_VGA:
			pageTable->release((void *) region->va, region->size);
			break;
	}
	
	delete region;
	
	lock.unlock();
}

Region *AddressSpace::findType(uint32 type) {
	lock.lock();
	
	Region *n = 0;
	for (n=head; n; n = n->next) {
		if (n->type == type) {
			break;
		}
	}
		
	lock.unlock();
	
	return n;
}

Region *AddressSpace::findVirtual(uint32 va) {
	lock.lock();
	
	Region *n = 0;
	for (n=head; n; n = n->next) {
		if (va >= n->va && va < (n->va + n->size)) {
			break;
		}
	}
		
	lock.unlock();
	
	return n;
}

Region *AddressSpace::findPhysical(uint32 pa) {
	lock.lock();
	
	Region *n = 0;
	for (n=head; n; n = n->next) {
		if (pa >= n->pa && pa < (n->pa + n->size)) {
			break;
		}
	}
		
	lock.unlock();
	
	return n;
}

Region *AddressSpace::first(void) {
	return head;
}

Region *AddressSpace::last(void) {
	return tail;
}
